library(tidyverse)         # for graphing and data cleaning
library(tidymodels)        # for modeling
library(naniar)            # for analyzing missing values
library(vip)               # for variable importance plots
theme_set(theme_minimal()) # Lisa's favorite theme
hotels <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-02-11/hotels.csv')

When you finish the assignment, remove the # from the options chunk at the top, so that messages and warnings aren’t printed. If you are getting errors in your code, add error = TRUE so that the file knits. I would recommend not removing the # until you are completely finished.

Put it on GitHub!

From now on, GitHub should be part of your routine when doing assignments. I recommend making it part of your process anytime you are working in R, but I’ll make you show it’s part of your process for assignments.

Task: When you are finished with the assignment, post a link below to the GitHub repo for the assignment.

Machine Learning review and intro to tidymodels

Read through and follow along with the Machine Learning review with an intro to the tidymodels package posted on the Course Materials page.

Tasks:

  1. Read about the hotel booking data, hotels, on the Tidy Tuesday page it came from. There is also a link to an article from the original authors. The outcome we will be predicting is called is_canceled.
  • Without doing any analysis, what are some variables you think might be predictive and why?

deposit_type, previous_cancellations, and stays_in_week_nights could be predictive factors. If the booking is nonrefundable, the guests are less likely to cancel since they can’t get their money back. Also, if guest cancelled a lot of bookings before, they may have a higher chance to cancel the booking this time. Moreover, people who stay in week nights are more likely to travel for business and are less likely to cancel because the schedule is more fixed.

_ What are some problems that might exist with the data? You might think about how it was collected and who did the collecting.

Since not all the data is collected from bookings or change log database tables, mistakes can happen when merging other tables with existing ones. Moreover, we don’t the location of hotels and it is possible that all of them comes from a specific region and thus are not representitive.

  • If we construct a model, what type of conclusions will be able to draw from it?

We would find factors that relate to higher or lower possibility of hotel cancellation.

  1. Create some exploratory plots or table summaries of the variables in the dataset. Be sure to also examine missing values or other interesting values. You may want to adjust the fig.width and fig.height in the code chunk options.
hotels %>% 
  select(where(is.numeric)) %>% 
  pivot_longer(cols = everything(),
               names_to = "variable", 
               values_to = "value") %>% 
  ggplot(aes(x = value)) +
  geom_histogram(bins = 30) +
  facet_wrap(vars(variable), 
             scales = "free",
             nrow = 5)

hotels %>% 
  mutate(across(where(is.character), as.factor)) %>% 
  select(where(is.factor)) %>% 
  pivot_longer(cols = everything(),
               names_to = "variable", 
               values_to = "value") %>% 
  ggplot(aes(x = value)) +
  geom_bar() +
  facet_wrap(vars(variable), 
             scales = "free", 
             nrow = 4)

hotels %>% 
  add_n_miss() %>% 
  count(n_miss_all)
  1. First, we will do a couple things to get the data ready.
  • I did the following for you: made outcome a factor (needs to be that way for logistic regression), made all character variables factoors, removed the year variable and some reservation status variables, and removed cases with missing values (not NULLs but true missing values).

  • You need to split the data into a training and test set, stratifying on the outcome variable, is_canceled. Since we have a lot of data, split the data 50/50 between training and test. I have already set.seed() for you. Be sure to use hotels_mod in the splitting.

hotels_mod <- hotels %>% 
  mutate(is_canceled = as.factor(is_canceled)) %>% 
  mutate(across(where(is.character), as.factor)) %>% 
  select(-arrival_date_year,
         -reservation_status,
         -reservation_status_date) %>% 
  add_n_miss() %>% 
  filter(n_miss_all == 0) %>% 
  select(-n_miss_all)

set.seed(494)
hotels_split <- initial_split(data = hotels_mod, 
                             prop = .5,
                             strata = `is_canceled`)
hotels_training <- training(hotels_split)
hotels_testing <- testing(hotels_split)
  1. In this next step, we are going to do the pre-processing. Usually, I won’t tell you exactly what to do here, but for your first exercise, I’ll tell you the steps.
  • Set up the recipe with is_canceled as the outcome and all other variables as predictors (HINT: ~.).
  • Use a step_XXX() function or functions (I think there are other ways to do this, but I found step_mutate_at() easiest) to create some indicator variables for the following variables: children, babies, and previous_cancellations. So, the new variable should be a 1 if the original is more than 0 and 0 otherwise. Make sure you do this in a way that accounts for values that may be larger than any we see in the dataset.
  • For the agent and company variables, make new indicator variables that are 1 if they have a value of NULL and 0 otherwise. I also used step_mutate_at() for this, but there’s more ways you could do it.
  • Use fct_lump_n() inside step_mutate() to lump together countries that aren’t in the top 5 most occurring.
  • If you used new names for some of the new variables you created, then remove any variables that are no longer needed.
  • Use step_normalize() to center and scale all the non-categorical predictor variables. (Do this BEFORE creating dummy variables. When I tried to do it after, I ran into an error - I’m still investigating why.)
  • Create dummy variables for all factors/categorical predictor variables (make sure you have -all_outcomes() in this part!!).
  • Use the prep() and juice() functions to apply the steps to the training data just to check that everything went as planned.
hotels_recipe<-recipe(is_canceled~.,data = hotels_training) %>% 
  step_mutate(children = as.factor(as.numeric(children >0)),
                 babies = as.factor(as.numeric(babies>0)),
                 previous_cancellations = as.factor(as.numeric(previous_cancellations>0))) %>% 
  step_mutate(agent = as.factor(as.numeric(agent=="NULL")),
              company = as.factor(as.numeric(company=='NULL')),
              country = fct_lump_n(f = country, n = 5)) %>%
  step_normalize(all_predictors(), 
                 -all_nominal()) %>% 
  step_dummy(all_nominal(), 
             -all_outcomes()) 
# step_mutate_at(children, babies,previous_cancellations, fn=~as.factor(as.numeric(. >0)))  
hotels_recipe %>% 
  prep(hotels_training) %>%
  juice()
  1. In this step we will set up a LASSO model and workflow.
  • In general, why would we want to use LASSO instead of regular logistic regression? (HINT: think about what happens to the coefficients).

We will want to use that when there are a lot of factors in order to avoid overfitting.s

  • Define the model type, set the engine, set the penalty argument to tune() as a placeholder, and set the mode.
  • Create a workflow with the recipe and model.
hotels_lasso_mod<-
  logistic_reg(mixture = 1) %>% 
  set_engine("glmnet") %>% 
  set_args(penalty=tune()) %>% 
  set_mode("classification")
hotels_lasso_wf<-
  workflow() %>% 
  add_recipe(hotels_recipe) %>% 
  add_model(hotels_lasso_mod)
hotels_lasso_wf
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
## 
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 4 Recipe Steps
## 
## • step_mutate()
## • step_mutate()
## • step_normalize()
## • step_dummy()
## 
## ── Model ───────────────────────────────────────────────────────────────────────
## Logistic Regression Model Specification (classification)
## 
## Main Arguments:
##   penalty = tune()
##   mixture = 1
## 
## Computational engine: glmnet
  1. In this step, we’ll tune the model and fit the model using the best tuning parameter to the entire training dataset.
  • Create a 5-fold cross-validation sample. We’ll use this later. I have set the seed for you.
  • Use the grid_regular() function to create a grid of 10 potential penalty parameters (we’re keeping this sort of small because the dataset is pretty large). Use that with the 5-fold cv data to tune the model.
  • Use the tune_grid() function to fit the models with different tuning parameters to the different cross-validation sets.
  • Use the collect_metrics() function to collect all the metrics from the previous step and create a plot with the accuracy on the y-axis and the penalty term on the x-axis. Put the x-axis on the log scale.
  • Use the select_best() function to find the best tuning parameter, fit the model using that tuning parameter to the entire training set (HINT: finalize_workflow() and fit()), and display the model results using pull_workflow_fit() and tidy(). Are there some variables with coefficients of 0?
set.seed(494) # for reproducibility
# set up cv groups
hotels_cv<-vfold_cv(hotels_training,5)
#create grid
penalty_grid <- grid_regular(penalty(),
                             levels = 10)
# fit models with tuning parameters
hotels_lasso_tune <-
  hotels_lasso_wf %>% 
  tune_grid(
    resamples = hotels_cv,
    grid = penalty_grid
  )
# collect all metrics 
hotels_lasso_tune %>% 
  collect_metrics() %>% 
  filter(.metric == "accuracy") %>% 
  ggplot(aes(x = penalty, y = mean)) +
  geom_point() +
  geom_line() +
  scale_x_log10(
   breaks = scales::trans_breaks("log10", function(x) 10^x),
   labels = scales::trans_format("log10",scales::math_format(10^.x))) +
  labs(x = "penalty", y = "accuracy")

best_param <- hotels_lasso_tune %>% 
  select_best(metric = "accuracy")
hotels_lasso_final_wf <- hotels_lasso_wf %>% 
  finalize_workflow(best_param)
hotels_lasso_final_mod <- hotels_lasso_final_wf %>% 
  fit(data = hotels_training)

hotels_lasso_final_mod %>% 
  pull_workflow_fit() %>% 
  tidy() 

arrival_date_month_Semptember, market_segment_undefined, distribution_channel_Undefined,assigned_room_type_L are zeros.

  1. Now that we have a model, let’s evaluate it a bit more. All we have looked at so far is the cross-validated accuracy from the previous step.
  • Create a variable importance graph. Which variables show up as the most important? Are you surprised?
hotels_lasso_final_mod %>% 
  pull_workflow_fit() %>% 
  vip()

reserved room type, deposite type, and assigned room type are shown as the top 3 most important variables. I’m not very surprised by the results. They all kind of make sense.

  • Use the last_fit() function to fit the final model and then apply it to the testing data. Report the metrics from the testing data using the collet_metrics() function. How do they compare to the cross-validated metrics?
hotels_lasso_test <- hotels_lasso_final_wf %>% 
  last_fit(hotels_split)
hotels_lasso_test %>% 
  collect_metrics()
hotels_lasso_tune %>% 
  collect_metrics() %>% 
  filter(.metric == "accuracy",
         penalty == best_param$penalty) 

The accuracy for testing data is close to the cross-validated metrics. Therefore, our model probably is not over-fitting.

  • Use the collect_predictions() function to find the predicted probabilities and classes for the test data. Save this to a new dataset called preds. Then, use the conf_mat() function from dials (part of tidymodels) to create a confusion matrix showing the predicted classes vs. the true classes. Compute the true positive rate (sensitivity), true negative rate (specificity), and accuracy. See this Wikipedia reference if you (like me) tend to forget these definitions. Also keep in mind that a “positive” in this case is a cancellation (those are the 1’s).
preds<-collect_predictions(hotels_lasso_test)
conf_mat(data = preds,truth = is_canceled,estimate = .pred_class)
##           Truth
## Prediction     0     1
##          0 34175  7776
##          1  3408 14334
# true postive rate
14334/(14334+7776)
## [1] 0.6483039
# true negative rate 
34175/(34175+3408)
## [1] 0.9093207
# accuracy
(34175+14334)/(34175+14334+7776+3408)
## [1] 0.8126413
  • Use the preds dataset you just created to create a density plot of the predicted probabilities of canceling (the variable is called .pred_1), filling by is_canceled. Use an alpha = .5 and color = NA in the geom_density(). Answer these questions:
 preds %>% 
  ggplot(aes(.pred_1,fill = `is_canceled`)) +
  geom_density(alpha = 0.5,
               color = NA) +
  geom_vline(xintercept = 0.5)

a. What would this graph look like for a model with an accuracy that was close to 1?

If the accuracy is 1, either part won’t pass the vertical line where x equals to 0.5.

  1. Our predictions are classified as canceled if their predicted probability of canceling is greater than .5. If we wanted to have a high true positive rate, should we make the cutoff for predicted as canceled higher or lower than .5?

We should make the cutoff value lower.

  1. What happens to the true negative rate if we try to get a higher true positive rate?

The true negative rate will be lower.

  1. Let’s say that this model is going to be applied to bookings 14 days in advance of their arrival at each hotel, and someone who works for the hotel will make a phone call to the person who made the booking. During this phone call, they will try to assure that the person will be keeping their reservation or that they will be canceling in which case they can do that now and still have time to fill the room. How should the hotel go about deciding who to call? How could they measure whether it was worth the effort to do the calling? Can you think of another way they might use the model?

I think the hotel staff should make call to those who have refundable reservation, have assigned rooms, and have canceled the booking before, asking them how certain they are about the reservations. If they have refundable room and assigned rooms, for example, they are less likely to cancel so it is not worthy to call that group. Given the information, the hotel may over interpret the relationship and decide to make all rooms nonrefundable or make them all assigned rooms.

  1. How might you go about questioning and evaluating the model in terms of fairness? Are there any questions you would like to ask of the people who collected the data?

I may question the price and the locations of the hotels in order to examine the fairness of the study. As we know, many hotels are expensive and people live under poverty may be underestimated if the hotels in the dataset are all fancy ones located in big cities. Since prefenrences of the poor could be diffeent from the majority, it could change the result.

Bias and Fairness

Read Chapter 1: The Power Chapter of Data Feminism by Catherine D’Ignazio and Lauren Klein. Write a 4-6 sentence paragraph reflecting on this chapter. As you reflect, you might consider responding to these specific questions. We will also have a discussion about these questions in class on Thursday.

In general, I was surprised by many applications of AI mentioned in the article. I felt like in many of the cases, we are just too eagerly to get an anwser from the existing data instead of thinking where the data is from and its possible implications. Overall, i think having more diverse groups and improving awareness of minority groups in data science field will help a lot: in this case, people would actually spend time thinking about the implications rather than the pure results.

  • At the end of the “Matrix of Domination” section, they encourage us to “ask uncomfortable questions: who is doing the work of data science (and who is not)? Whose goals are prioritized in data science (and whose are not)? And who benefits from data science (and who is either overlooked or actively harmed)?” In general, how would you answer these questions? And why are they important?

The answer to who is doing the work of data acience can actually show by the data. As also mentioned in the article, minority groups like Latinix, black women are scarce in this data science field. Moreover, I think the goals for people who want to use data science to facilitate equality or do good things in general should be are prioritized and those that further deepen discriminations should not. In terms of who is overlooked, the reading mentioned both dominant and monority groups are kind of being harmed. Minority groups are harmed because there is not enough representative samples while dominant groups are imparied by being overlooked the right they also have. Those questions are important to answer because through them, we can actually see what’s the current problem with data science and thus should looking for ways to solve them.

  • Can you think of any examples of missing datasets, like those described in the “Data Science for Whom?” section? Or was there an example there that surprised you?

In the section where it talked about how police use a software that is trained by biased data really surprised me. If the police relies on the software, more unpriviledged people would be caught and their faces may use to furtehr train the model. In that case, a viscious cricle will be formed.

  • How did the examples in the “Data Science with Whose Interests and Goals?” section make you feel? What responsibility do companies have to prevent these things from occurring? Who is to blame?

I thought the example of Target is ridiculous. I felt like the that process, they are treating potential customers, which are women in this case, as a general object other than diverse human beings. To prevent this things from happening, I feel that the not only corporations but data scientists should take more respsonsibility when analyzing the data. As mentioned in the passage, we need to think what is outside of the box.

LS0tCnRpdGxlOiAnQXNzaWdubWVudCAjMicKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UpCmBgYAoKYGBge3IgbGlicmFyaWVzfQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICAgICAgICAjIGZvciBncmFwaGluZyBhbmQgZGF0YSBjbGVhbmluZwpsaWJyYXJ5KHRpZHltb2RlbHMpICAgICAgICAjIGZvciBtb2RlbGluZwpsaWJyYXJ5KG5hbmlhcikgICAgICAgICAgICAjIGZvciBhbmFseXppbmcgbWlzc2luZyB2YWx1ZXMKbGlicmFyeSh2aXApICAgICAgICAgICAgICAgIyBmb3IgdmFyaWFibGUgaW1wb3J0YW5jZSBwbG90cwp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKSAjIExpc2EncyBmYXZvcml0ZSB0aGVtZQpgYGAKCmBgYHtyIGRhdGF9CmhvdGVscyA8LSByZWFkcjo6cmVhZF9jc3YoJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yZm9yZGF0YXNjaWVuY2UvdGlkeXR1ZXNkYXkvbWFzdGVyL2RhdGEvMjAyMC8yMDIwLTAyLTExL2hvdGVscy5jc3YnKQpgYGAKCgpXaGVuIHlvdSBmaW5pc2ggdGhlIGFzc2lnbm1lbnQsIHJlbW92ZSB0aGUgYCNgIGZyb20gdGhlIG9wdGlvbnMgY2h1bmsgYXQgdGhlIHRvcCwgc28gdGhhdCBtZXNzYWdlcyBhbmQgd2FybmluZ3MgYXJlbid0IHByaW50ZWQuIElmIHlvdSBhcmUgZ2V0dGluZyBlcnJvcnMgaW4geW91ciBjb2RlLCBhZGQgYGVycm9yID0gVFJVRWAgc28gdGhhdCB0aGUgZmlsZSBrbml0cy4gSSB3b3VsZCByZWNvbW1lbmQgbm90IHJlbW92aW5nIHRoZSBgI2AgdW50aWwgeW91IGFyZSBjb21wbGV0ZWx5IGZpbmlzaGVkLgoKIyMgUHV0IGl0IG9uIEdpdEh1YiEgICAgICAgIAoKRnJvbSBub3cgb24sIEdpdEh1YiBzaG91bGQgYmUgcGFydCBvZiB5b3VyIHJvdXRpbmUgd2hlbiBkb2luZyBhc3NpZ25tZW50cy4gSSByZWNvbW1lbmQgbWFraW5nIGl0IHBhcnQgb2YgeW91ciBwcm9jZXNzIGFueXRpbWUgeW91IGFyZSB3b3JraW5nIGluIFIsIGJ1dCBJJ2xsIG1ha2UgeW91IHNob3cgaXQncyBwYXJ0IG9mIHlvdXIgcHJvY2VzcyBmb3IgYXNzaWdubWVudHMuCgoqKlRhc2sqKjogV2hlbiB5b3UgYXJlIGZpbmlzaGVkIHdpdGggdGhlIGFzc2lnbm1lbnQsIHBvc3QgYSBsaW5rIGJlbG93IHRvIHRoZSBHaXRIdWIgcmVwbyBmb3IgdGhlIGFzc2lnbm1lbnQuIAoKCiMjIE1hY2hpbmUgTGVhcm5pbmcgcmV2aWV3IGFuZCBpbnRybyB0byBgdGlkeW1vZGVsc2AKClJlYWQgdGhyb3VnaCBhbmQgZm9sbG93IGFsb25nIHdpdGggdGhlIFtNYWNoaW5lIExlYXJuaW5nIHJldmlldyB3aXRoIGFuIGludHJvIHRvIHRoZSBgdGlkeW1vZGVsc2AgcGFja2FnZV0oaHR0cHM6Ly9hZHZhbmNlZC1kcy1pbi1yLm5ldGxpZnkuYXBwL3Bvc3RzLzIwMjEtMDMtMTYtbWwtcmV2aWV3LykgcG9zdGVkIG9uIHRoZSBDb3Vyc2UgTWF0ZXJpYWxzIHBhZ2UuIAoKKipUYXNrcyoqOgoKMS4gUmVhZCBhYm91dCB0aGUgaG90ZWwgYm9va2luZyBkYXRhLCBgaG90ZWxzYCwgb24gdGhlIFtUaWR5IFR1ZXNkYXkgcGFnZV0oaHR0cHM6Ly9naXRodWIuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9ibG9iL21hc3Rlci9kYXRhLzIwMjAvMjAyMC0wMi0xMS9yZWFkbWUubWQpIGl0IGNhbWUgZnJvbS4gVGhlcmUgaXMgYWxzbyBhIGxpbmsgdG8gYW4gYXJ0aWNsZSBmcm9tIHRoZSBvcmlnaW5hbCBhdXRob3JzLiBUaGUgb3V0Y29tZSB3ZSB3aWxsIGJlIHByZWRpY3RpbmcgaXMgY2FsbGVkIGBpc19jYW5jZWxlZGAuIAogIC0gV2l0aG91dCBkb2luZyBhbnkgYW5hbHlzaXMsIHdoYXQgYXJlIHNvbWUgdmFyaWFibGVzIHlvdSB0aGluayBtaWdodCBiZSBwcmVkaWN0aXZlIGFuZCB3aHk/ICAgIAoKICA+IGRlcG9zaXRfdHlwZSwgcHJldmlvdXNfY2FuY2VsbGF0aW9ucywgYW5kIHN0YXlzX2luX3dlZWtfbmlnaHRzIGNvdWxkIGJlIHByZWRpY3RpdmUgZmFjdG9ycy4gSWYgdGhlIGJvb2tpbmcgaXMgbm9ucmVmdW5kYWJsZSwgdGhlIGd1ZXN0cyBhcmUgbGVzcyBsaWtlbHkgdG8gY2FuY2VsIHNpbmNlIHRoZXkgY2FuJ3QgZ2V0IHRoZWlyIG1vbmV5IGJhY2suIEFsc28sIGlmIGd1ZXN0IGNhbmNlbGxlZCBhIGxvdCBvZiBib29raW5ncyBiZWZvcmUsIHRoZXkgbWF5IGhhdmUgYSBoaWdoZXIgY2hhbmNlIHRvIGNhbmNlbCB0aGUgYm9va2luZyB0aGlzIHRpbWUuIE1vcmVvdmVyLCBwZW9wbGUgd2hvIHN0YXkgaW4gd2VlayBuaWdodHMgYXJlIG1vcmUgbGlrZWx5IHRvIHRyYXZlbCBmb3IgYnVzaW5lc3MgYW5kIGFyZSBsZXNzIGxpa2VseSB0byBjYW5jZWwgYmVjYXVzZSB0aGUgc2NoZWR1bGUgaXMgbW9yZSBmaXhlZC4gCgogIF8gV2hhdCBhcmUgc29tZSBwcm9ibGVtcyB0aGF0IG1pZ2h0IGV4aXN0IHdpdGggdGhlIGRhdGE/IFlvdSBtaWdodCB0aGluayBhYm91dCBob3cgaXQgd2FzIGNvbGxlY3RlZCBhbmQgd2hvIGRpZCB0aGUgY29sbGVjdGluZy4gIAogIAogID4gU2luY2Ugbm90IGFsbCB0aGUgZGF0YSBpcyBjb2xsZWN0ZWQgZnJvbSBib29raW5ncyBvciBjaGFuZ2UgbG9nIGRhdGFiYXNlIHRhYmxlcywgbWlzdGFrZXMgY2FuIGhhcHBlbiB3aGVuIG1lcmdpbmcgb3RoZXIgdGFibGVzIHdpdGggZXhpc3Rpbmcgb25lcy4gTW9yZW92ZXIsIHdlIGRvbid0IHRoZSBsb2NhdGlvbiBvZiBob3RlbHMgYW5kIGl0IGlzIHBvc3NpYmxlIHRoYXQgYWxsIG9mIHRoZW0gY29tZXMgZnJvbSBhIHNwZWNpZmljIHJlZ2lvbiBhbmQgdGh1cyBhcmUgbm90IHJlcHJlc2VudGl0aXZlLiAKICAKICAtIElmIHdlIGNvbnN0cnVjdCBhIG1vZGVsLCB3aGF0IHR5cGUgb2YgY29uY2x1c2lvbnMgd2lsbCBiZSBhYmxlIHRvIGRyYXcgZnJvbSBpdD8gCiAgCiAgPiBXZSB3b3VsZCBmaW5kIGZhY3RvcnMgdGhhdCByZWxhdGUgdG8gaGlnaGVyIG9yIGxvd2VyIHBvc3NpYmlsaXR5IG9mIGhvdGVsIGNhbmNlbGxhdGlvbi4gCiAgCjIuIENyZWF0ZSBzb21lIGV4cGxvcmF0b3J5IHBsb3RzIG9yIHRhYmxlIHN1bW1hcmllcyBvZiB0aGUgdmFyaWFibGVzIGluIHRoZSBkYXRhc2V0LiBCZSBzdXJlIHRvIGFsc28gZXhhbWluZSBtaXNzaW5nIHZhbHVlcyBvciBvdGhlciBpbnRlcmVzdGluZyB2YWx1ZXMuIFlvdSBtYXkgd2FudCB0byBhZGp1c3QgdGhlIGBmaWcud2lkdGhgIGFuZCBgZmlnLmhlaWdodGAgaW4gdGhlIGNvZGUgY2h1bmsgb3B0aW9ucy4gIApgYGB7cn0KaG90ZWxzICU+JSAKICBzZWxlY3Qod2hlcmUoaXMubnVtZXJpYykpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGUiLCAKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lIAogIGdncGxvdChhZXMoeCA9IHZhbHVlKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAzMCkgKwogIGZhY2V0X3dyYXAodmFycyh2YXJpYWJsZSksIAogICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiLAogICAgICAgICAgICAgbnJvdyA9IDUpCmBgYApgYGB7cn0KaG90ZWxzICU+JSAKICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLmNoYXJhY3RlciksIGFzLmZhY3RvcikpICU+JSAKICBzZWxlY3Qod2hlcmUoaXMuZmFjdG9yKSkgJT4lIAogIHBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLAogICAgICAgICAgICAgICBuYW1lc190byA9ICJ2YXJpYWJsZSIsIAogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gdmFsdWUpKSArCiAgZ2VvbV9iYXIoKSArCiAgZmFjZXRfd3JhcCh2YXJzKHZhcmlhYmxlKSwgCiAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZSIsIAogICAgICAgICAgICAgbnJvdyA9IDQpCmBgYApgYGB7cn0KaG90ZWxzICU+JSAKICBhZGRfbl9taXNzKCkgJT4lIAogIGNvdW50KG5fbWlzc19hbGwpCmBgYAoKCjMuIEZpcnN0LCB3ZSB3aWxsIGRvIGEgY291cGxlIHRoaW5ncyB0byBnZXQgdGhlIGRhdGEgcmVhZHkuIAoKKiBJIGRpZCB0aGUgZm9sbG93aW5nIGZvciB5b3U6IG1hZGUgb3V0Y29tZSBhIGZhY3RvciAobmVlZHMgdG8gYmUgdGhhdCB3YXkgZm9yIGxvZ2lzdGljIHJlZ3Jlc3Npb24pLCBtYWRlIGFsbCBjaGFyYWN0ZXIgdmFyaWFibGVzIGZhY3Rvb3JzLCByZW1vdmVkIHRoZSB5ZWFyIHZhcmlhYmxlIGFuZCBzb21lIHJlc2VydmF0aW9uIHN0YXR1cyB2YXJpYWJsZXMsIGFuZCByZW1vdmVkIGNhc2VzIHdpdGggbWlzc2luZyB2YWx1ZXMgKG5vdCBOVUxMcyBidXQgdHJ1ZSBtaXNzaW5nIHZhbHVlcykuCgoqIFlvdSBuZWVkIHRvIHNwbGl0IHRoZSBkYXRhIGludG8gYSB0cmFpbmluZyBhbmQgdGVzdCBzZXQsIHN0cmF0aWZ5aW5nIG9uIHRoZSBvdXRjb21lIHZhcmlhYmxlLCBgaXNfY2FuY2VsZWRgLiBTaW5jZSB3ZSBoYXZlIGEgbG90IG9mIGRhdGEsIHNwbGl0IHRoZSBkYXRhIDUwLzUwIGJldHdlZW4gdHJhaW5pbmcgYW5kIHRlc3QuIEkgaGF2ZSBhbHJlYWR5IGBzZXQuc2VlZCgpYCBmb3IgeW91LiBCZSBzdXJlIHRvIHVzZSBgaG90ZWxzX21vZGAgaW4gdGhlIHNwbGl0dGluZy4gCgpgYGB7cn0KaG90ZWxzX21vZCA8LSBob3RlbHMgJT4lIAogIG11dGF0ZShpc19jYW5jZWxlZCA9IGFzLmZhY3Rvcihpc19jYW5jZWxlZCkpICU+JSAKICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLmNoYXJhY3RlciksIGFzLmZhY3RvcikpICU+JSAKICBzZWxlY3QoLWFycml2YWxfZGF0ZV95ZWFyLAogICAgICAgICAtcmVzZXJ2YXRpb25fc3RhdHVzLAogICAgICAgICAtcmVzZXJ2YXRpb25fc3RhdHVzX2RhdGUpICU+JSAKICBhZGRfbl9taXNzKCkgJT4lIAogIGZpbHRlcihuX21pc3NfYWxsID09IDApICU+JSAKICBzZWxlY3QoLW5fbWlzc19hbGwpCgpzZXQuc2VlZCg0OTQpCmhvdGVsc19zcGxpdCA8LSBpbml0aWFsX3NwbGl0KGRhdGEgPSBob3RlbHNfbW9kLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9wID0gLjUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyYXRhID0gYGlzX2NhbmNlbGVkYCkKaG90ZWxzX3RyYWluaW5nIDwtIHRyYWluaW5nKGhvdGVsc19zcGxpdCkKaG90ZWxzX3Rlc3RpbmcgPC0gdGVzdGluZyhob3RlbHNfc3BsaXQpCmBgYAoKNC4gSW4gdGhpcyBuZXh0IHN0ZXAsIHdlIGFyZSBnb2luZyB0byBkbyB0aGUgcHJlLXByb2Nlc3NpbmcuIFVzdWFsbHksIEkgd29uJ3QgdGVsbCB5b3UgZXhhY3RseSB3aGF0IHRvIGRvIGhlcmUsIGJ1dCBmb3IgeW91ciBmaXJzdCBleGVyY2lzZSwgSSdsbCB0ZWxsIHlvdSB0aGUgc3RlcHMuIAoKKiBTZXQgdXAgdGhlIHJlY2lwZSB3aXRoIGBpc19jYW5jZWxlZGAgYXMgdGhlIG91dGNvbWUgYW5kIGFsbCBvdGhlciB2YXJpYWJsZXMgYXMgcHJlZGljdG9ycyAoSElOVDogYH4uYCkuICAKKiBVc2UgYSBgc3RlcF9YWFgoKWAgZnVuY3Rpb24gb3IgZnVuY3Rpb25zIChJIHRoaW5rIHRoZXJlIGFyZSBvdGhlciB3YXlzIHRvIGRvIHRoaXMsIGJ1dCBJIGZvdW5kIGBzdGVwX211dGF0ZV9hdCgpYCBlYXNpZXN0KSB0byBjcmVhdGUgc29tZSBpbmRpY2F0b3IgdmFyaWFibGVzIGZvciB0aGUgZm9sbG93aW5nIHZhcmlhYmxlczogYGNoaWxkcmVuYCwgYGJhYmllc2AsIGFuZCBgcHJldmlvdXNfY2FuY2VsbGF0aW9uc2AuIFNvLCB0aGUgbmV3IHZhcmlhYmxlIHNob3VsZCBiZSBhIDEgaWYgdGhlIG9yaWdpbmFsIGlzIG1vcmUgdGhhbiAwIGFuZCAwIG90aGVyd2lzZS4gTWFrZSBzdXJlIHlvdSBkbyB0aGlzIGluIGEgd2F5IHRoYXQgYWNjb3VudHMgZm9yIHZhbHVlcyB0aGF0IG1heSBiZSBsYXJnZXIgdGhhbiBhbnkgd2Ugc2VlIGluIHRoZSBkYXRhc2V0LiAKKiBGb3IgdGhlIGBhZ2VudGAgYW5kIGBjb21wYW55YCB2YXJpYWJsZXMsIG1ha2UgbmV3IGluZGljYXRvciB2YXJpYWJsZXMgdGhhdCBhcmUgMSBpZiB0aGV5IGhhdmUgYSB2YWx1ZSBvZiBgTlVMTGAgYW5kIDAgb3RoZXJ3aXNlLiBJIGFsc28gdXNlZCBgc3RlcF9tdXRhdGVfYXQoKWAgZm9yIHRoaXMsIGJ1dCB0aGVyZSdzIG1vcmUgd2F5cyB5b3UgY291bGQgZG8gaXQuCiogVXNlIGBmY3RfbHVtcF9uKClgIGluc2lkZSBgc3RlcF9tdXRhdGUoKWAgdG8gbHVtcCB0b2dldGhlciBjb3VudHJpZXMgdGhhdCBhcmVuJ3QgaW4gdGhlIHRvcCA1IG1vc3Qgb2NjdXJyaW5nLiAKKiBJZiB5b3UgdXNlZCBuZXcgbmFtZXMgZm9yIHNvbWUgb2YgdGhlIG5ldyB2YXJpYWJsZXMgeW91IGNyZWF0ZWQsIHRoZW4gcmVtb3ZlIGFueSB2YXJpYWJsZXMgdGhhdCBhcmUgbm8gbG9uZ2VyIG5lZWRlZC4gCiogVXNlIGBzdGVwX25vcm1hbGl6ZSgpYCB0byBjZW50ZXIgYW5kIHNjYWxlIGFsbCB0aGUgbm9uLWNhdGVnb3JpY2FsIHByZWRpY3RvciB2YXJpYWJsZXMuIChEbyB0aGlzIEJFRk9SRSBjcmVhdGluZyBkdW1teSB2YXJpYWJsZXMuIFdoZW4gSSB0cmllZCB0byBkbyBpdCBhZnRlciwgSSByYW4gaW50byBhbiBlcnJvciAtIEknbSBzdGlsbCBbaW52ZXN0aWdhdGluZ10oaHR0cHM6Ly9jb21tdW5pdHkucnN0dWRpby5jb20vdC90aWR5bW9kZWxzLXNlZS1ub3Rlcy1lcnJvci1idXQtb25seS13aXRoLXN0ZXAteHh4LWZ1bmN0aW9ucy1pbi1hLWNlcnRhaW4tb3JkZXIvMTE1MDA2KSB3aHkuKQoqIENyZWF0ZSBkdW1teSB2YXJpYWJsZXMgZm9yIGFsbCBmYWN0b3JzL2NhdGVnb3JpY2FsIHByZWRpY3RvciB2YXJpYWJsZXMgKG1ha2Ugc3VyZSB5b3UgaGF2ZSBgLWFsbF9vdXRjb21lcygpYCBpbiB0aGlzIHBhcnQhISkuICAKKiBVc2UgdGhlIGBwcmVwKClgIGFuZCBganVpY2UoKWAgZnVuY3Rpb25zIHRvIGFwcGx5IHRoZSBzdGVwcyB0byB0aGUgdHJhaW5pbmcgZGF0YSBqdXN0IHRvIGNoZWNrIHRoYXQgZXZlcnl0aGluZyB3ZW50IGFzIHBsYW5uZWQuCmBgYHtyfQpob3RlbHNfcmVjaXBlPC1yZWNpcGUoaXNfY2FuY2VsZWR+LixkYXRhID0gaG90ZWxzX3RyYWluaW5nKSAlPiUgCiAgc3RlcF9tdXRhdGUoY2hpbGRyZW4gPSBhcy5mYWN0b3IoYXMubnVtZXJpYyhjaGlsZHJlbiA+MCkpLAogICAgICAgICAgICAgICAgIGJhYmllcyA9IGFzLmZhY3Rvcihhcy5udW1lcmljKGJhYmllcz4wKSksCiAgICAgICAgICAgICAgICAgcHJldmlvdXNfY2FuY2VsbGF0aW9ucyA9IGFzLmZhY3Rvcihhcy5udW1lcmljKHByZXZpb3VzX2NhbmNlbGxhdGlvbnM+MCkpKSAlPiUgCiAgc3RlcF9tdXRhdGUoYWdlbnQgPSBhcy5mYWN0b3IoYXMubnVtZXJpYyhhZ2VudD09Ik5VTEwiKSksCiAgICAgICAgICAgICAgY29tcGFueSA9IGFzLmZhY3Rvcihhcy5udW1lcmljKGNvbXBhbnk9PSdOVUxMJykpLAogICAgICAgICAgICAgIGNvdW50cnkgPSBmY3RfbHVtcF9uKGYgPSBjb3VudHJ5LCBuID0gNSkpICU+JQogIHN0ZXBfbm9ybWFsaXplKGFsbF9wcmVkaWN0b3JzKCksIAogICAgICAgICAgICAgICAgIC1hbGxfbm9taW5hbCgpKSAlPiUgCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCAKICAgICAgICAgICAgIC1hbGxfb3V0Y29tZXMoKSkgCiMgc3RlcF9tdXRhdGVfYXQoY2hpbGRyZW4sIGJhYmllcyxwcmV2aW91c19jYW5jZWxsYXRpb25zLCBmbj1+YXMuZmFjdG9yKGFzLm51bWVyaWMoLiA+MCkpKSAgCmBgYAoKCgoKYGBge3J9CmhvdGVsc19yZWNpcGUgJT4lIAogIHByZXAoaG90ZWxzX3RyYWluaW5nKSAlPiUKICBqdWljZSgpCmBgYAoKNS4gSW4gdGhpcyBzdGVwIHdlIHdpbGwgc2V0IHVwIGEgTEFTU08gbW9kZWwgYW5kIHdvcmtmbG93LgoKKiBJbiBnZW5lcmFsLCB3aHkgd291bGQgd2Ugd2FudCB0byB1c2UgTEFTU08gaW5zdGVhZCBvZiByZWd1bGFyIGxvZ2lzdGljIHJlZ3Jlc3Npb24/IChISU5UOiB0aGluayBhYm91dCB3aGF0IGhhcHBlbnMgdG8gdGhlIGNvZWZmaWNpZW50cykuICAKCj4gV2Ugd2lsbCB3YW50IHRvIHVzZSB0aGF0IHdoZW4gdGhlcmUgYXJlIGEgbG90IG9mIGZhY3RvcnMgaW4gb3JkZXIgdG8gYXZvaWQgb3ZlcmZpdHRpbmcucwoKKiBEZWZpbmUgdGhlIG1vZGVsIHR5cGUsIHNldCB0aGUgZW5naW5lLCBzZXQgdGhlIGBwZW5hbHR5YCBhcmd1bWVudCB0byBgdHVuZSgpYCBhcyBhIHBsYWNlaG9sZGVyLCBhbmQgc2V0IHRoZSBtb2RlLiAgCiogQ3JlYXRlIGEgd29ya2Zsb3cgd2l0aCB0aGUgcmVjaXBlIGFuZCBtb2RlbC4gIApgYGB7cn0KaG90ZWxzX2xhc3NvX21vZDwtCiAgbG9naXN0aWNfcmVnKG1peHR1cmUgPSAxKSAlPiUgCiAgc2V0X2VuZ2luZSgiZ2xtbmV0IikgJT4lIAogIHNldF9hcmdzKHBlbmFsdHk9dHVuZSgpKSAlPiUgCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikKYGBgCmBgYHtyfQpob3RlbHNfbGFzc29fd2Y8LQogIHdvcmtmbG93KCkgJT4lIAogIGFkZF9yZWNpcGUoaG90ZWxzX3JlY2lwZSkgJT4lIAogIGFkZF9tb2RlbChob3RlbHNfbGFzc29fbW9kKQpgYGAKYGBge3J9CmhvdGVsc19sYXNzb193ZgpgYGAKCjYuIEluIHRoaXMgc3RlcCwgd2UnbGwgdHVuZSB0aGUgbW9kZWwgYW5kIGZpdCB0aGUgbW9kZWwgdXNpbmcgdGhlIGJlc3QgdHVuaW5nIHBhcmFtZXRlciB0byB0aGUgZW50aXJlIHRyYWluaW5nIGRhdGFzZXQuCgoqIENyZWF0ZSBhIDUtZm9sZCBjcm9zcy12YWxpZGF0aW9uIHNhbXBsZS4gV2UnbGwgdXNlIHRoaXMgbGF0ZXIuIEkgaGF2ZSBzZXQgdGhlIHNlZWQgZm9yIHlvdS4gIAoqIFVzZSB0aGUgYGdyaWRfcmVndWxhcigpYCBmdW5jdGlvbiB0byBjcmVhdGUgYSBncmlkIG9mIDEwIHBvdGVudGlhbCBwZW5hbHR5IHBhcmFtZXRlcnMgKHdlJ3JlIGtlZXBpbmcgdGhpcyBzb3J0IG9mIHNtYWxsIGJlY2F1c2UgdGhlIGRhdGFzZXQgaXMgcHJldHR5IGxhcmdlKS4gVXNlIHRoYXQgd2l0aCB0aGUgNS1mb2xkIGN2IGRhdGEgdG8gdHVuZSB0aGUgbW9kZWwuICAKKiBVc2UgdGhlIGB0dW5lX2dyaWQoKWAgZnVuY3Rpb24gdG8gZml0IHRoZSBtb2RlbHMgd2l0aCBkaWZmZXJlbnQgdHVuaW5nIHBhcmFtZXRlcnMgdG8gdGhlIGRpZmZlcmVudCBjcm9zcy12YWxpZGF0aW9uIHNldHMuICAKKiBVc2UgdGhlIGBjb2xsZWN0X21ldHJpY3MoKWAgZnVuY3Rpb24gdG8gY29sbGVjdCBhbGwgdGhlIG1ldHJpY3MgZnJvbSB0aGUgcHJldmlvdXMgc3RlcCBhbmQgY3JlYXRlIGEgcGxvdCB3aXRoIHRoZSBhY2N1cmFjeSBvbiB0aGUgeS1heGlzIGFuZCB0aGUgcGVuYWx0eSB0ZXJtIG9uIHRoZSB4LWF4aXMuIFB1dCB0aGUgeC1heGlzIG9uIHRoZSBsb2cgc2NhbGUuICAKKiBVc2UgdGhlIGBzZWxlY3RfYmVzdCgpYCBmdW5jdGlvbiB0byBmaW5kIHRoZSBiZXN0IHR1bmluZyBwYXJhbWV0ZXIsIGZpdCB0aGUgbW9kZWwgdXNpbmcgdGhhdCB0dW5pbmcgcGFyYW1ldGVyIHRvIHRoZSBlbnRpcmUgdHJhaW5pbmcgc2V0IChISU5UOiBgZmluYWxpemVfd29ya2Zsb3coKWAgYW5kIGBmaXQoKWApLCBhbmQgZGlzcGxheSB0aGUgbW9kZWwgcmVzdWx0cyB1c2luZyBgcHVsbF93b3JrZmxvd19maXQoKWAgYW5kIGB0aWR5KClgLiBBcmUgdGhlcmUgc29tZSB2YXJpYWJsZXMgd2l0aCBjb2VmZmljaWVudHMgb2YgMD8KCmBgYHtyfQpzZXQuc2VlZCg0OTQpICMgZm9yIHJlcHJvZHVjaWJpbGl0eQojIHNldCB1cCBjdiBncm91cHMKaG90ZWxzX2N2PC12Zm9sZF9jdihob3RlbHNfdHJhaW5pbmcsNSkKI2NyZWF0ZSBncmlkCnBlbmFsdHlfZ3JpZCA8LSBncmlkX3JlZ3VsYXIocGVuYWx0eSgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IDEwKQojIGZpdCBtb2RlbHMgd2l0aCB0dW5pbmcgcGFyYW1ldGVycwpob3RlbHNfbGFzc29fdHVuZSA8LQogIGhvdGVsc19sYXNzb193ZiAlPiUgCiAgdHVuZV9ncmlkKAogICAgcmVzYW1wbGVzID0gaG90ZWxzX2N2LAogICAgZ3JpZCA9IHBlbmFsdHlfZ3JpZAogICkKIyBjb2xsZWN0IGFsbCBtZXRyaWNzIApob3RlbHNfbGFzc29fdHVuZSAlPiUgCiAgY29sbGVjdF9tZXRyaWNzKCkgJT4lIAogIGZpbHRlcigubWV0cmljID09ICJhY2N1cmFjeSIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBwZW5hbHR5LCB5ID0gbWVhbikpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICBzY2FsZV94X2xvZzEwKAogICBicmVha3MgPSBzY2FsZXM6OnRyYW5zX2JyZWFrcygibG9nMTAiLCBmdW5jdGlvbih4KSAxMF54KSwKICAgbGFiZWxzID0gc2NhbGVzOjp0cmFuc19mb3JtYXQoImxvZzEwIixzY2FsZXM6Om1hdGhfZm9ybWF0KDEwXi54KSkpICsKICBsYWJzKHggPSAicGVuYWx0eSIsIHkgPSAiYWNjdXJhY3kiKQpgYGAKYGBge3J9CmJlc3RfcGFyYW0gPC0gaG90ZWxzX2xhc3NvX3R1bmUgJT4lIAogIHNlbGVjdF9iZXN0KG1ldHJpYyA9ICJhY2N1cmFjeSIpCmBgYApgYGB7cn0KaG90ZWxzX2xhc3NvX2ZpbmFsX3dmIDwtIGhvdGVsc19sYXNzb193ZiAlPiUgCiAgZmluYWxpemVfd29ya2Zsb3coYmVzdF9wYXJhbSkKYGBgCgpgYGB7cn0KaG90ZWxzX2xhc3NvX2ZpbmFsX21vZCA8LSBob3RlbHNfbGFzc29fZmluYWxfd2YgJT4lIAogIGZpdChkYXRhID0gaG90ZWxzX3RyYWluaW5nKQoKaG90ZWxzX2xhc3NvX2ZpbmFsX21vZCAlPiUgCiAgcHVsbF93b3JrZmxvd19maXQoKSAlPiUgCiAgdGlkeSgpIApgYGAKCj4gYXJyaXZhbF9kYXRlX21vbnRoX1NlbXB0ZW1iZXIsIG1hcmtldF9zZWdtZW50X3VuZGVmaW5lZCwgZGlzdHJpYnV0aW9uX2NoYW5uZWxfVW5kZWZpbmVkLGFzc2lnbmVkX3Jvb21fdHlwZV9MIGFyZSB6ZXJvcy4gCgo3LiBOb3cgdGhhdCB3ZSBoYXZlIGEgbW9kZWwsIGxldCdzIGV2YWx1YXRlIGl0IGEgYml0IG1vcmUuIEFsbCB3ZSBoYXZlIGxvb2tlZCBhdCBzbyBmYXIgaXMgdGhlIGNyb3NzLXZhbGlkYXRlZCBhY2N1cmFjeSBmcm9tIHRoZSBwcmV2aW91cyBzdGVwLiAKCiogQ3JlYXRlIGEgdmFyaWFibGUgaW1wb3J0YW5jZSBncmFwaC4gV2hpY2ggdmFyaWFibGVzIHNob3cgdXAgYXMgdGhlIG1vc3QgaW1wb3J0YW50PyBBcmUgeW91IHN1cnByaXNlZD8gIApgYGB7cn0KaG90ZWxzX2xhc3NvX2ZpbmFsX21vZCAlPiUgCiAgcHVsbF93b3JrZmxvd19maXQoKSAlPiUgCiAgdmlwKCkKYGBgCgo+IHJlc2VydmVkIHJvb20gdHlwZSwgZGVwb3NpdGUgdHlwZSwgYW5kIGFzc2lnbmVkIHJvb20gdHlwZSBhcmUgc2hvd24gYXMgdGhlIHRvcCAzIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcy4gSSdtIG5vdCB2ZXJ5IHN1cnByaXNlZCBieSB0aGUgcmVzdWx0cy4gVGhleSBhbGwga2luZCBvZiBtYWtlIHNlbnNlLiAKCiogVXNlIHRoZSBgbGFzdF9maXQoKWAgZnVuY3Rpb24gdG8gZml0IHRoZSBmaW5hbCBtb2RlbCBhbmQgdGhlbiBhcHBseSBpdCB0byB0aGUgdGVzdGluZyBkYXRhLiBSZXBvcnQgdGhlIG1ldHJpY3MgZnJvbSB0aGUgdGVzdGluZyBkYXRhIHVzaW5nIHRoZSBgY29sbGV0X21ldHJpY3MoKWAgZnVuY3Rpb24uIEhvdyBkbyB0aGV5IGNvbXBhcmUgdG8gdGhlIGNyb3NzLXZhbGlkYXRlZCBtZXRyaWNzPwoKYGBge3J9CmhvdGVsc19sYXNzb190ZXN0IDwtIGhvdGVsc19sYXNzb19maW5hbF93ZiAlPiUgCiAgbGFzdF9maXQoaG90ZWxzX3NwbGl0KQpgYGAKYGBge3J9CmhvdGVsc19sYXNzb190ZXN0ICU+JSAKICBjb2xsZWN0X21ldHJpY3MoKQpgYGAKYGBge3J9CmhvdGVsc19sYXNzb190dW5lICU+JSAKICBjb2xsZWN0X21ldHJpY3MoKSAlPiUgCiAgZmlsdGVyKC5tZXRyaWMgPT0gImFjY3VyYWN5IiwKICAgICAgICAgcGVuYWx0eSA9PSBiZXN0X3BhcmFtJHBlbmFsdHkpIApgYGAKCj4gVGhlIGFjY3VyYWN5IGZvciB0ZXN0aW5nIGRhdGEgaXMgY2xvc2UgdG8gdGhlIGNyb3NzLXZhbGlkYXRlZCBtZXRyaWNzLiBUaGVyZWZvcmUsIG91ciBtb2RlbCBwcm9iYWJseSBpcyBub3Qgb3Zlci1maXR0aW5nLgoKKiBVc2UgdGhlIGBjb2xsZWN0X3ByZWRpY3Rpb25zKClgIGZ1bmN0aW9uIHRvIGZpbmQgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIGFuZCBjbGFzc2VzIGZvciB0aGUgdGVzdCBkYXRhLiBTYXZlIHRoaXMgdG8gYSBuZXcgZGF0YXNldCBjYWxsZWQgYHByZWRzYC4gVGhlbiwgdXNlIHRoZSBgY29uZl9tYXQoKWAgZnVuY3Rpb24gZnJvbSBgZGlhbHNgIChwYXJ0IG9mIGB0aWR5bW9kZWxzYCkgdG8gY3JlYXRlIGEgY29uZnVzaW9uIG1hdHJpeCBzaG93aW5nIHRoZSBwcmVkaWN0ZWQgY2xhc3NlcyB2cy4gdGhlIHRydWUgY2xhc3Nlcy4gQ29tcHV0ZSB0aGUgdHJ1ZSBwb3NpdGl2ZSByYXRlIChzZW5zaXRpdml0eSksIHRydWUgbmVnYXRpdmUgcmF0ZSAoc3BlY2lmaWNpdHkpLCBhbmQgYWNjdXJhY3kuIFNlZSB0aGlzIFtXaWtpcGVkaWFdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0NvbmZ1c2lvbl9tYXRyaXgpIHJlZmVyZW5jZSBpZiB5b3UgKGxpa2UgbWUpIHRlbmQgdG8gZm9yZ2V0IHRoZXNlIGRlZmluaXRpb25zLiBBbHNvIGtlZXAgaW4gbWluZCB0aGF0IGEgInBvc2l0aXZlIiBpbiB0aGlzIGNhc2UgaXMgYSBjYW5jZWxsYXRpb24gKHRob3NlIGFyZSB0aGUgMSdzKS4gIApgYGB7cn0KcHJlZHM8LWNvbGxlY3RfcHJlZGljdGlvbnMoaG90ZWxzX2xhc3NvX3Rlc3QpCmBgYApgYGB7cn0KY29uZl9tYXQoZGF0YSA9IHByZWRzLHRydXRoID0gaXNfY2FuY2VsZWQsZXN0aW1hdGUgPSAucHJlZF9jbGFzcykKYGBgCmBgYHtyfQojIHRydWUgcG9zdGl2ZSByYXRlCjE0MzM0LygxNDMzNCs3Nzc2KQojIHRydWUgbmVnYXRpdmUgcmF0ZSAKMzQxNzUvKDM0MTc1KzM0MDgpCiMgYWNjdXJhY3kKKDM0MTc1KzE0MzM0KS8oMzQxNzUrMTQzMzQrNzc3NiszNDA4KQpgYGAKCiogVXNlIHRoZSBgcHJlZHNgIGRhdGFzZXQgeW91IGp1c3QgY3JlYXRlZCB0byBjcmVhdGUgYSBkZW5zaXR5IHBsb3Qgb2YgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIG9mIGNhbmNlbGluZyAodGhlIHZhcmlhYmxlIGlzIGNhbGxlZCBgLnByZWRfMWApLCBmaWxsaW5nIGJ5IGBpc19jYW5jZWxlZGAuIFVzZSBhbiBgYWxwaGEgPSAuNWAgYW5kIGBjb2xvciA9IE5BYCBpbiB0aGUgYGdlb21fZGVuc2l0eSgpYC4gQW5zd2VyIHRoZXNlIHF1ZXN0aW9uczogCmBgYHtyfQoKIHByZWRzICU+JSAKICBnZ3Bsb3QoYWVzKC5wcmVkXzEsZmlsbCA9IGBpc19jYW5jZWxlZGApKSArCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC41LAogICAgICAgICAgICAgICBjb2xvciA9IE5BKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMC41KQpgYGAKYS4gV2hhdCB3b3VsZCB0aGlzIGdyYXBoIGxvb2sgbGlrZSBmb3IgYSBtb2RlbCB3aXRoIGFuIGFjY3VyYWN5IHRoYXQgd2FzIGNsb3NlIHRvIDE/ICAKCj4gSWYgdGhlIGFjY3VyYWN5IGlzIDEsIGVpdGhlciBwYXJ0IHdvbid0IHBhc3MgdGhlIHZlcnRpY2FsIGxpbmUgd2hlcmUgeCBlcXVhbHMgdG8gMC41LiAKCmIuIE91ciBwcmVkaWN0aW9ucyBhcmUgY2xhc3NpZmllZCBhcyBjYW5jZWxlZCBpZiB0aGVpciBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgb2YgY2FuY2VsaW5nIGlzIGdyZWF0ZXIgdGhhbiAuNS4gSWYgd2Ugd2FudGVkIHRvIGhhdmUgYSBoaWdoIHRydWUgcG9zaXRpdmUgcmF0ZSwgc2hvdWxkIHdlIG1ha2UgdGhlIGN1dG9mZiBmb3IgcHJlZGljdGVkIGFzIGNhbmNlbGVkIGhpZ2hlciBvciBsb3dlciB0aGFuIC41PyAgCgo+IFdlIHNob3VsZCBtYWtlIHRoZSBjdXRvZmYgdmFsdWUgbG93ZXIuIAoKYy4gV2hhdCBoYXBwZW5zIHRvIHRoZSB0cnVlIG5lZ2F0aXZlIHJhdGUgaWYgd2UgdHJ5IHRvIGdldCBhIGhpZ2hlciB0cnVlIHBvc2l0aXZlIHJhdGU/IAoKPiBUaGUgdHJ1ZSBuZWdhdGl2ZSByYXRlIHdpbGwgYmUgbG93ZXIuIAoKCjguIExldCdzIHNheSB0aGF0IHRoaXMgbW9kZWwgaXMgZ29pbmcgdG8gYmUgYXBwbGllZCB0byBib29raW5ncyAxNCBkYXlzIGluIGFkdmFuY2Ugb2YgdGhlaXIgYXJyaXZhbCBhdCBlYWNoIGhvdGVsLCBhbmQgc29tZW9uZSB3aG8gd29ya3MgZm9yIHRoZSBob3RlbCB3aWxsIG1ha2UgYSBwaG9uZSBjYWxsIHRvIHRoZSBwZXJzb24gd2hvIG1hZGUgdGhlIGJvb2tpbmcuIER1cmluZyB0aGlzIHBob25lIGNhbGwsIHRoZXkgd2lsbCB0cnkgdG8gYXNzdXJlIHRoYXQgdGhlIHBlcnNvbiB3aWxsIGJlIGtlZXBpbmcgdGhlaXIgcmVzZXJ2YXRpb24gb3IgdGhhdCB0aGV5IHdpbGwgYmUgY2FuY2VsaW5nIGluIHdoaWNoIGNhc2UgdGhleSBjYW4gZG8gdGhhdCBub3cgYW5kIHN0aWxsIGhhdmUgdGltZSB0byBmaWxsIHRoZSByb29tLiBIb3cgc2hvdWxkIHRoZSBob3RlbCBnbyBhYm91dCBkZWNpZGluZyB3aG8gdG8gY2FsbD8gSG93IGNvdWxkIHRoZXkgbWVhc3VyZSB3aGV0aGVyIGl0IHdhcyB3b3J0aCB0aGUgZWZmb3J0IHRvIGRvIHRoZSBjYWxsaW5nPyBDYW4geW91IHRoaW5rIG9mIGFub3RoZXIgd2F5IHRoZXkgbWlnaHQgdXNlIHRoZSBtb2RlbD8gCgo+IEkgdGhpbmsgdGhlIGhvdGVsIHN0YWZmIHNob3VsZCBtYWtlIGNhbGwgdG8gdGhvc2Ugd2hvIGhhdmUgcmVmdW5kYWJsZSByZXNlcnZhdGlvbiwgaGF2ZSBhc3NpZ25lZCByb29tcywgYW5kIGhhdmUgY2FuY2VsZWQgdGhlIGJvb2tpbmcgYmVmb3JlLCBhc2tpbmcgdGhlbSBob3cgY2VydGFpbiB0aGV5IGFyZSBhYm91dCB0aGUgcmVzZXJ2YXRpb25zLiBJZiB0aGV5IGhhdmUgcmVmdW5kYWJsZSByb29tIGFuZCBhc3NpZ25lZCByb29tcywgZm9yIGV4YW1wbGUsIHRoZXkgYXJlIGxlc3MgbGlrZWx5IHRvIGNhbmNlbCBzbyBpdCBpcyBub3Qgd29ydGh5IHRvIGNhbGwgdGhhdCBncm91cC4gR2l2ZW4gdGhlIGluZm9ybWF0aW9uLCB0aGUgaG90ZWwgbWF5IG92ZXIgaW50ZXJwcmV0IHRoZSByZWxhdGlvbnNoaXAgYW5kIGRlY2lkZSB0byBtYWtlIGFsbCByb29tcyBub25yZWZ1bmRhYmxlIG9yIG1ha2UgdGhlbSBhbGwgYXNzaWduZWQgcm9vbXMuIAoKOS4gSG93IG1pZ2h0IHlvdSBnbyBhYm91dCBxdWVzdGlvbmluZyBhbmQgZXZhbHVhdGluZyB0aGUgbW9kZWwgaW4gdGVybXMgb2YgZmFpcm5lc3M/IEFyZSB0aGVyZSBhbnkgcXVlc3Rpb25zIHlvdSB3b3VsZCBsaWtlIHRvIGFzayBvZiB0aGUgcGVvcGxlIHdobyBjb2xsZWN0ZWQgdGhlIGRhdGE/IAoKPiBJIG1heSBxdWVzdGlvbiB0aGUgcHJpY2UgYW5kIHRoZSBsb2NhdGlvbnMgb2YgdGhlIGhvdGVscyBpbiBvcmRlciB0byBleGFtaW5lIHRoZSBmYWlybmVzcyBvZiB0aGUgc3R1ZHkuIEFzIHdlIGtub3csIG1hbnkgaG90ZWxzIGFyZSBleHBlbnNpdmUgYW5kIHBlb3BsZSBsaXZlIHVuZGVyIHBvdmVydHkgbWF5IGJlIHVuZGVyZXN0aW1hdGVkIGlmIHRoZSBob3RlbHMgaW4gdGhlIGRhdGFzZXQgIGFyZSBhbGwgZmFuY3kgb25lcyBsb2NhdGVkIGluIGJpZyBjaXRpZXMuIFNpbmNlIHByZWZlbnJlbmNlcyBvZiB0aGUgcG9vciBjb3VsZCBiZSBkaWZmZWVudCBmcm9tIHRoZSBtYWpvcml0eSwgaXQgY291bGQgY2hhbmdlIHRoZSByZXN1bHQuIAoKCgojIyBCaWFzIGFuZCBGYWlybmVzcwoKUmVhZCBbQ2hhcHRlciAxOiBUaGUgUG93ZXIgQ2hhcHRlcl0oaHR0cHM6Ly9kYXRhLWZlbWluaXNtLm1pdHByZXNzLm1pdC5lZHUvcHViL3ZpOG9ieGg3L3JlbGVhc2UvNCkgb2YgRGF0YSBGZW1pbmlzbSBieSBDYXRoZXJpbmUgRCdJZ25hemlvIGFuZCBMYXVyZW4gS2xlaW4uIFdyaXRlIGEgNC02IHNlbnRlbmNlIHBhcmFncmFwaCByZWZsZWN0aW5nIG9uIHRoaXMgY2hhcHRlci4gQXMgeW91IHJlZmxlY3QsIHlvdSBtaWdodCBjb25zaWRlciByZXNwb25kaW5nIHRvIHRoZXNlIHNwZWNpZmljIHF1ZXN0aW9ucy4gV2Ugd2lsbCBhbHNvIGhhdmUgYSBkaXNjdXNzaW9uIGFib3V0IHRoZXNlIHF1ZXN0aW9ucyBpbiBjbGFzcyBvbiBUaHVyc2RheS4KCj4gSW4gZ2VuZXJhbCwgSSB3YXMgc3VycHJpc2VkIGJ5IG1hbnkgYXBwbGljYXRpb25zIG9mIEFJIG1lbnRpb25lZCBpbiB0aGUgYXJ0aWNsZS4gSSBmZWx0IGxpa2UgaW4gbWFueSBvZiB0aGUgY2FzZXMsIHdlIGFyZSBqdXN0IHRvbyBlYWdlcmx5IHRvIGdldCBhbiBhbndzZXIgZnJvbSB0aGUgZXhpc3RpbmcgZGF0YSBpbnN0ZWFkIG9mIHRoaW5raW5nIHdoZXJlIHRoZSBkYXRhIGlzIGZyb20gYW5kIGl0cyBwb3NzaWJsZSBpbXBsaWNhdGlvbnMuIE92ZXJhbGwsIGkgdGhpbmsgaGF2aW5nIG1vcmUgZGl2ZXJzZSBncm91cHMgYW5kIGltcHJvdmluZyBhd2FyZW5lc3Mgb2YgbWlub3JpdHkgZ3JvdXBzIGluIGRhdGEgc2NpZW5jZSBmaWVsZCB3aWxsIGhlbHAgYSBsb3Q6IGluIHRoaXMgY2FzZSwgcGVvcGxlIHdvdWxkIGFjdHVhbGx5IHNwZW5kIHRpbWUgdGhpbmtpbmcgYWJvdXQgdGhlIGltcGxpY2F0aW9ucyByYXRoZXIgdGhhbiB0aGUgcHVyZSByZXN1bHRzLiAKCiogQXQgdGhlIGVuZCBvZiB0aGUgIk1hdHJpeCBvZiBEb21pbmF0aW9uIiBzZWN0aW9uLCB0aGV5IGVuY291cmFnZSB1cyB0byAiYXNrIHVuY29tZm9ydGFibGUgcXVlc3Rpb25zOiB3aG8gaXMgZG9pbmcgdGhlIHdvcmsgb2YgZGF0YSBzY2llbmNlIChhbmQgd2hvIGlzIG5vdCk/IFdob3NlIGdvYWxzIGFyZSBwcmlvcml0aXplZCBpbiBkYXRhIHNjaWVuY2UgKGFuZCB3aG9zZSBhcmUgbm90KT8gQW5kIHdobyBiZW5lZml0cyBmcm9tIGRhdGEgc2NpZW5jZSAoYW5kIHdobyBpcyBlaXRoZXIgb3Zlcmxvb2tlZCBvciBhY3RpdmVseSBoYXJtZWQpPyIgSW4gZ2VuZXJhbCwgaG93IHdvdWxkIHlvdSBhbnN3ZXIgdGhlc2UgcXVlc3Rpb25zPyBBbmQgd2h5IGFyZSB0aGV5IGltcG9ydGFudD8gIAoKPiBUaGUgYW5zd2VyIHRvIHdobyBpcyBkb2luZyB0aGUgd29yayBvZiBkYXRhIGFjaWVuY2UgY2FuIGFjdHVhbGx5IHNob3cgYnkgdGhlIGRhdGEuIEFzIGFsc28gbWVudGlvbmVkIGluIHRoZSBhcnRpY2xlLCBtaW5vcml0eSBncm91cHMgbGlrZSBMYXRpbml4LCBibGFjayB3b21lbiBhcmUgc2NhcmNlIGluIHRoaXMgZGF0YSBzY2llbmNlIGZpZWxkLiBNb3Jlb3ZlciwgSSB0aGluayB0aGUgZ29hbHMgZm9yIHBlb3BsZSB3aG8gd2FudCB0byB1c2UgZGF0YSBzY2llbmNlIHRvIGZhY2lsaXRhdGUgZXF1YWxpdHkgb3IgZG8gZ29vZCB0aGluZ3MgaW4gZ2VuZXJhbCBzaG91bGQgYmUgYXJlIHByaW9yaXRpemVkIGFuZCB0aG9zZSB0aGF0IGZ1cnRoZXIgZGVlcGVuIGRpc2NyaW1pbmF0aW9ucyBzaG91bGQgbm90LiBJbiB0ZXJtcyBvZiB3aG8gaXMgb3Zlcmxvb2tlZCwgdGhlIHJlYWRpbmcgbWVudGlvbmVkIGJvdGggZG9taW5hbnQgYW5kIG1vbm9yaXR5IGdyb3VwcyBhcmUga2luZCBvZiBiZWluZyBoYXJtZWQuIE1pbm9yaXR5IGdyb3VwcyBhcmUgaGFybWVkIGJlY2F1c2UgdGhlcmUgaXMgbm90IGVub3VnaCByZXByZXNlbnRhdGl2ZSBzYW1wbGVzIHdoaWxlIGRvbWluYW50IGdyb3VwcyBhcmUgaW1wYXJpZWQgYnkgYmVpbmcgb3Zlcmxvb2tlZCB0aGUgcmlnaHQgdGhleSBhbHNvIGhhdmUuIFRob3NlIHF1ZXN0aW9ucyBhcmUgaW1wb3J0YW50IHRvIGFuc3dlciBiZWNhdXNlIHRocm91Z2ggdGhlbSwgd2UgY2FuIGFjdHVhbGx5IHNlZSB3aGF0J3MgdGhlIGN1cnJlbnQgcHJvYmxlbSB3aXRoIGRhdGEgc2NpZW5jZSBhbmQgdGh1cyBzaG91bGQgbG9va2luZyBmb3Igd2F5cyB0byBzb2x2ZSB0aGVtLiAKCiogQ2FuIHlvdSB0aGluayBvZiBhbnkgZXhhbXBsZXMgb2YgbWlzc2luZyBkYXRhc2V0cywgbGlrZSB0aG9zZSBkZXNjcmliZWQgaW4gdGhlICJEYXRhIFNjaWVuY2UgZm9yIFdob20/IiBzZWN0aW9uPyBPciB3YXMgdGhlcmUgYW4gZXhhbXBsZSB0aGVyZSB0aGF0IHN1cnByaXNlZCB5b3U/ICAKCj4gSW4gdGhlIHNlY3Rpb24gd2hlcmUgaXQgdGFsa2VkIGFib3V0IGhvdyBwb2xpY2UgdXNlIGEgc29mdHdhcmUgdGhhdCBpcyB0cmFpbmVkIGJ5IGJpYXNlZCBkYXRhIHJlYWxseSBzdXJwcmlzZWQgbWUuIElmIHRoZSBwb2xpY2UgcmVsaWVzIG9uIHRoZSBzb2Z0d2FyZSwgbW9yZSB1bnByaXZpbGVkZ2VkIHBlb3BsZSB3b3VsZCBiZSBjYXVnaHQgYW5kIHRoZWlyIGZhY2VzIG1heSB1c2UgdG8gZnVydGVociB0cmFpbiB0aGUgbW9kZWwuIEluIHRoYXQgY2FzZSwgYSB2aXNjaW91cyBjcmljbGUgd2lsbCBiZSBmb3JtZWQuIAoKKiBIb3cgZGlkIHRoZSBleGFtcGxlcyBpbiB0aGUgIkRhdGEgU2NpZW5jZSB3aXRoIFdob3NlIEludGVyZXN0cyBhbmQgR29hbHM/IiBzZWN0aW9uIG1ha2UgeW91IGZlZWw/IFdoYXQgcmVzcG9uc2liaWxpdHkgZG8gY29tcGFuaWVzIGhhdmUgdG8gcHJldmVudCB0aGVzZSB0aGluZ3MgZnJvbSBvY2N1cnJpbmc/IFdobyBpcyB0byBibGFtZT8KCj4gSSB0aG91Z2h0IHRoZSBleGFtcGxlIG9mIFRhcmdldCBpcyByaWRpY3Vsb3VzLiBJIGZlbHQgbGlrZSB0aGUgdGhhdCBwcm9jZXNzLCB0aGV5IGFyZSB0cmVhdGluZyBwb3RlbnRpYWwgY3VzdG9tZXJzLCB3aGljaCBhcmUgd29tZW4gaW4gdGhpcyBjYXNlLCBhcyBhIGdlbmVyYWwgb2JqZWN0IG90aGVyIHRoYW4gZGl2ZXJzZSBodW1hbiBiZWluZ3MuIFRvIHByZXZlbnQgdGhpcyB0aGluZ3MgZnJvbSBoYXBwZW5pbmcsIEkgZmVlbCB0aGF0IHRoZSBub3Qgb25seSBjb3Jwb3JhdGlvbnMgYnV0IGRhdGEgc2NpZW50aXN0cyBzaG91bGQgdGFrZSBtb3JlIHJlc3Bzb25zaWJpbGl0eSB3aGVuIGFuYWx5emluZyB0aGUgZGF0YS4gQXMgbWVudGlvbmVkIGluIHRoZSBwYXNzYWdlLCB3ZSBuZWVkIHRvIHRoaW5rIHdoYXQgaXMgb3V0c2lkZSBvZiB0aGUgYm94LiAKCg==